/*
 * Copyright 2011 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.swf.encoder
{
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import sweezy.swf.ISWFElement;
	import sweezy.swf.ITag;
	import sweezy.swf.SWF;
	import sweezy.swf.action.IAction;
	import sweezy.swf.constant.BitmapType;
	import sweezy.swf.constant.FillStyleType;
	import sweezy.swf.constant.JoinStyle;
	import sweezy.swf.constant.MorphFillStyleType;
	import sweezy.swf.constant.SWFVersion;
	import sweezy.swf.constant.SoundFormat;
	import sweezy.swf.io.SWFOutput;
	import sweezy.swf.structure.ABCData;
	import sweezy.swf.structure.ActionRecords;
	import sweezy.swf.structure.AlphaBitmapData;
	import sweezy.swf.structure.AlphaColorMapData;
	import sweezy.swf.structure.Asset;
	import sweezy.swf.structure.BevelFilter;
	import sweezy.swf.structure.BitmapData15;
	import sweezy.swf.structure.BitmapData24;
	import sweezy.swf.structure.BlurFilter;
	import sweezy.swf.structure.ButtonCondAction;
	import sweezy.swf.structure.ButtonRecord;
	import sweezy.swf.structure.CXForm;
	import sweezy.swf.structure.CXFormWithAlpha;
	import sweezy.swf.structure.ClipActionRecord;
	import sweezy.swf.structure.ClipActions;
	import sweezy.swf.structure.ClipEventFlags;
	import sweezy.swf.structure.ColorMapData;
	import sweezy.swf.structure.ColorMatrixFilter;
	import sweezy.swf.structure.ConvolutionFilter;
	import sweezy.swf.structure.CurvedEdgeRecord;
	import sweezy.swf.structure.DropShadowFilter;
	import sweezy.swf.structure.FillStyle;
	import sweezy.swf.structure.FocalGradient;
	import sweezy.swf.structure.FrameLabelData;
	import sweezy.swf.structure.GlowFilter;
	import sweezy.swf.structure.GlyphEntry;
	import sweezy.swf.structure.Gradient;
	import sweezy.swf.structure.GradientBevelFilter;
	import sweezy.swf.structure.GradientGlowFilter;
	import sweezy.swf.structure.GradientRecord;
	import sweezy.swf.structure.Header;
	import sweezy.swf.structure.IFilter;
	import sweezy.swf.structure.IShapeRecord;
	import sweezy.swf.structure.KerningRecord;
	import sweezy.swf.structure.LanguageCode;
	import sweezy.swf.structure.LineStyle;
	import sweezy.swf.structure.LineStyle2;
	import sweezy.swf.structure.LineStyleBase;
	import sweezy.swf.structure.Matrix;
	import sweezy.swf.structure.MorphFillStyle;
	import sweezy.swf.structure.MorphGradientRecord;
	import sweezy.swf.structure.MorphLineStyle;
	import sweezy.swf.structure.MorphLineStyle2;
	import sweezy.swf.structure.MorphLineStyleBase;
	import sweezy.swf.structure.RGB;
	import sweezy.swf.structure.RGBA;
	import sweezy.swf.structure.Rect;
	import sweezy.swf.structure.SceneData;
	import sweezy.swf.structure.Shape;
	import sweezy.swf.structure.ShapeWithStyle;
	import sweezy.swf.structure.SoundEnvelope;
	import sweezy.swf.structure.SoundInfo;
	import sweezy.swf.structure.StraightEdgeRecord;
	import sweezy.swf.structure.StyleChangeRecord;
	import sweezy.swf.structure.Symbol;
	import sweezy.swf.structure.TextRecord;
	import sweezy.swf.structure.ZoneData;
	import sweezy.swf.structure.ZoneRecord;
	import sweezy.swf.tag.CSMTextSettings;
	import sweezy.swf.tag.DebugID;
	import sweezy.swf.tag.DefineBinaryData;
	import sweezy.swf.tag.DefineBits;
	import sweezy.swf.tag.DefineBitsJPEG2;
	import sweezy.swf.tag.DefineBitsJPEG3;
	import sweezy.swf.tag.DefineBitsJPEG4;
	import sweezy.swf.tag.DefineBitsLossless;
	import sweezy.swf.tag.DefineBitsLossless2;
	import sweezy.swf.tag.DefineButton;
	import sweezy.swf.tag.DefineButton2;
	import sweezy.swf.tag.DefineButtonCxform;
	import sweezy.swf.tag.DefineButtonSound;
	import sweezy.swf.tag.DefineEditText;
	import sweezy.swf.tag.DefineFont;
	import sweezy.swf.tag.DefineFont2;
	import sweezy.swf.tag.DefineFont23Base;
	import sweezy.swf.tag.DefineFont3;
	import sweezy.swf.tag.DefineFont4;
	import sweezy.swf.tag.DefineFontAlignZones;
	import sweezy.swf.tag.DefineFontInfo;
	import sweezy.swf.tag.DefineFontInfo2;
	import sweezy.swf.tag.DefineFontName;
	import sweezy.swf.tag.DefineMorphShape;
	import sweezy.swf.tag.DefineMorphShape2;
	import sweezy.swf.tag.DefineScalingGrid;
	import sweezy.swf.tag.DefineSceneAndFrameLabelData;
	import sweezy.swf.tag.DefineShape;
	import sweezy.swf.tag.DefineShape2;
	import sweezy.swf.tag.DefineShape3;
	import sweezy.swf.tag.DefineShape4;
	import sweezy.swf.tag.DefineSound;
	import sweezy.swf.tag.DefineSprite;
	import sweezy.swf.tag.DefineText;
	import sweezy.swf.tag.DefineText2;
	import sweezy.swf.tag.DefineVideoStream;
	import sweezy.swf.tag.DoABC;
	import sweezy.swf.tag.DoABC2;
	import sweezy.swf.tag.DoAction;
	import sweezy.swf.tag.DoInitAction;
	import sweezy.swf.tag.EnableDebugger;
	import sweezy.swf.tag.EnableDebugger2;
	import sweezy.swf.tag.End;
	import sweezy.swf.tag.ExportAssets;
	import sweezy.swf.tag.FileAttributes;
	import sweezy.swf.tag.FrameLabel;
	import sweezy.swf.tag.ImportAssets;
	import sweezy.swf.tag.ImportAssets2;
	import sweezy.swf.tag.JPEGTables;
	import sweezy.swf.tag.Metadata;
	import sweezy.swf.tag.PlaceObject;
	import sweezy.swf.tag.PlaceObject2;
	import sweezy.swf.tag.PlaceObject3;
	import sweezy.swf.tag.ProductInfo;
	import sweezy.swf.tag.Protect;
	import sweezy.swf.tag.RemoveObject;
	import sweezy.swf.tag.RemoveObject2;
	import sweezy.swf.tag.ScriptLimits;
	import sweezy.swf.tag.SetBackgroundColor;
	import sweezy.swf.tag.SetTabIndex;
	import sweezy.swf.tag.ShowFrame;
	import sweezy.swf.tag.SoundStreamBlock;
	import sweezy.swf.tag.SoundStreamHead;
	import sweezy.swf.tag.SoundStreamHead2;
	import sweezy.swf.tag.StartSound;
	import sweezy.swf.tag.StartSound2;
	import sweezy.swf.tag.SymbolClass;
	import sweezy.swf.tag.UnknownTag;
	import sweezy.swf.tag.VideoFrame;
	import sweezy.swf.visitor.SWFElementVisitor;

	internal class SWFEncoderBase extends SWFElementVisitor
	{

		private var _bytesPool:Array;

		private var _compilerType:uint;

		private var _out:SWFOutput;

		private var _swf:SWF;

		private var _tagStack:Array;

		private var _tmpBytes:ByteArray;

		private var _version:uint;

		public function SWFEncoderBase(bytes:ByteArray)
		{
			_out = new SWFOutput(bytes);
			_bytesPool = [];
			_tagStack = [];
		}

		override public function visitABCData(element:ABCData):void
		{
			writeBytes(element.data);
		}

		override public function visitActionRecords(element:ActionRecords):void
		{
			var bytes:ByteArray = new ByteArray();
			var encoder:ActionEncoder = new ActionEncoder(_version, bytes);

			for each (var action:IAction in element.records)
			{
				encoder.visit(action);
			}

			bytes.position = 0;
			bytes.endian = Endian.BIG_ENDIAN;
			writeBytes(bytes);
		}

		override public function visitAlphaBitmapData(element:AlphaBitmapData):void
		{
			writeBytes(element.bitmapPixelData);
		}

		override public function visitAlphaColorMapData(element:AlphaColorMapData):void
		{
			if (element.colormapPixelData !== null && element.colorTableRGBA !== null)
			{
				for each (var rgba:RGBA in element.colorTableRGBA)
				{
					accept(rgba);
				}
				writeBytes(element.colormapPixelData);
			}
		}

		override public function visitBevelFilter(element:BevelFilter):void
		{
			accept(element.shadowColor);
			accept(element.highlightColor);
			_out.writeFIXED(element.blurX);
			_out.writeFIXED(element.blurY);
			_out.writeFIXED(element.angle);
			_out.writeFIXED(element.distance);
			_out.writeFIXED8(element.strength);
			_out.writeFLAG(element.innerShadow);
			_out.writeFLAG(element.knockout);
			_out.writeFLAG(element.compositeSource);
			_out.writeFLAG(element.onTop);
			_out.writeUB(4, element.passes);
		}

		override public function visitBitmapData15(element:BitmapData15):void
		{
			writeBytes(element.bitmapPixelData);
		}

		override public function visitBitmapData24(element:BitmapData24):void
		{
			writeBytes(element.bitmapPixelData);
		}

		override public function visitBlurFilter(element:BlurFilter):void
		{
			_out.writeFIXED(element.blurX);
			_out.writeFIXED(element.blurY);
			_out.writeUB(5, element.passes);
			_out.writeUB(3, 0);
		}

		override public function visitButtonCondAction(element:ButtonCondAction):void
		{
			_out.writeFLAG(element.condIdleToOverDown);
			_out.writeFLAG(element.condOutDownToIdle);
			_out.writeFLAG(element.condOutDownToOverDown);
			_out.writeFLAG(element.condOverDownToOutDown);
			_out.writeFLAG(element.condOverDownToOverUp);
			_out.writeFLAG(element.condOverUpToOverDown);
			_out.writeFLAG(element.condOverUpToIdle);
			_out.writeFLAG(element.condIdleToOverUp);
			_out.writeUB(7, element.condKeyPress);
			_out.writeFLAG(element.condOverDownToIdle);
			accept(element.actions);
		}

		override public function visitButtonRecord(element:ButtonRecord):void
		{
			_out.resetCursor();
			_out.writeUB(2, 0);
			_out.writeFLAG(element.buttonHasBlendMode);
			_out.writeFLAG(element.buttonHasFilterList);
			_out.writeFLAG(element.buttonStateHitTest);
			_out.writeFLAG(element.buttonStateDown);
			_out.writeFLAG(element.buttonStateOver);
			_out.writeFLAG(element.buttonStateUp);

			_out.writeUI16(element.characterId);
			_out.writeUI16(element.placeDepth);
			accept(element.placeMatrix);

			if (element.defineButton2)
			{
				accept(element.colorTransform);

				if (element.buttonHasFilterList)
				{
					encodeFilterList(element.filterList);
				}
				if (element.buttonHasBlendMode)
				{
					_out.writeUI8(element.blendMode);
				}
			}
		}

		override public function visitCSMTextSettings(element:CSMTextSettings):void
		{
			_out.writeUI16(element.textId);
			_out.writeUB(2, element.useFlashType);
			_out.writeUB(3, element.gridFit);
			_out.writeUB(3, 0);
			_out.writeFLOAT32(element.thickness);
			_out.writeFLOAT32(element.sharpness);
			_out.writeUI8(0);
		}

		override public function visitCXForm(element:CXForm):void
		{
			_out.resetCursor();

			_out.writeFLAG(element.hasAdd);
			_out.writeFLAG(element.hasMult);

			var len:uint = Math.max(getMinSBits(element.minBits, element.redMultTerm, element.greenMultTerm, element.blueMultTerm), getMinSBits(element.minBits, element.redAddTerm, element.greenAddTerm, element.
				blueAddTerm));
			_out.writeUB(4, len);

			if (element.hasMult)
			{
				_out.writeSB(len, element.redMultTerm);
				_out.writeSB(len, element.greenMultTerm);
				_out.writeSB(len, element.blueMultTerm);
			}
			if (element.hasAdd)
			{
				_out.writeSB(len, element.redAddTerm);
				_out.writeSB(len, element.greenAddTerm);
				_out.writeSB(len, element.blueAddTerm);
			}
		}

		override public function visitCXFormWithAlpha(element:CXFormWithAlpha):void
		{
			_out.resetCursor();

			_out.writeFLAG(element.hasAdd);
			_out.writeFLAG(element.hasMult);

			var len:uint = Math.max(getMinSBits(element.minBits, element.redMultTerm, element.greenMultTerm, element.blueMultTerm, element.alphaMultTerm), getMinSBits(element.minBits, element.redAddTerm, element.
				greenAddTerm, element.blueAddTerm, element.alphaAddTerm));
			_out.writeUB(4, len);
			if (element.hasMult)
			{
				_out.writeSB(len, element.redMultTerm);
				_out.writeSB(len, element.greenMultTerm);
				_out.writeSB(len, element.blueMultTerm);
				_out.writeSB(len, element.alphaMultTerm);
			}
			if (element.hasAdd)
			{
				_out.writeSB(len, element.redAddTerm);
				_out.writeSB(len, element.greenAddTerm);
				_out.writeSB(len, element.blueAddTerm);
				_out.writeSB(len, element.alphaAddTerm);
			}
		}

		override public function visitClipActionRecord(element:ClipActionRecord):void
		{
			accept(element.eventFlags);

			pushBytes();
			if (element.eventFlags.clipEventKeyPress)
			{
				_out.writeUI8(element.keyCode);
			}

			accept(element.actions);

			var bytes:ByteArray = popBytes();
			_out.writeUI32(bytes.length);
			writeBytes(bytes);
		}

		override public function visitClipActions(element:ClipActions):void
		{
			_out.writeUI16(0);
			accept(element.allEventFlags);

			for each (var record:ClipActionRecord in element.clipActionRecords)
			{
				accept(record);
			}

			if (_version >= SWFVersion.VERSION_6)
			{
				_out.writeUI32(0);
			}
			else
			{
				_out.writeUI16(0);
			}
		}

		override public function visitClipEventFlags(element:ClipEventFlags):void
		{
			_out.resetCursor();

			_out.writeFLAG(element.clipEventKeyUp);
			_out.writeFLAG(element.clipEventKeyDown);
			_out.writeFLAG(element.clipEventMouseUp);
			_out.writeFLAG(element.clipEventMouseDown);
			_out.writeFLAG(element.clipEventMouseMove);
			_out.writeFLAG(element.clipEventUnload);
			_out.writeFLAG(element.clipEventEnterFrame);
			_out.writeFLAG(element.clipEventLoad);
			_out.writeFLAG(element.clipEventDragOver);
			_out.writeFLAG(element.clipEventRollOut);
			_out.writeFLAG(element.clipEventRollOver);
			_out.writeFLAG(element.clipEventReleaseOutside);
			_out.writeFLAG(element.clipEventRelease);
			_out.writeFLAG(element.clipEventPress);
			_out.writeFLAG(element.clipEventInitialize);
			_out.writeFLAG(element.clipEventData);

			if (_version >= SWFVersion.VERSION_6)
			{
				_out.writeUB(5, 0);
				_out.writeFLAG(element.clipEventConstruct);
				_out.writeFLAG(element.clipEventKeyPress);
				_out.writeFLAG(element.clipEventDragOut);
				_out.writeUI8(0);
			}
		}

		override public function visitColorMapData(element:ColorMapData):void
		{
			for each (var rgb:RGB in element.colorTableRGB)
			{
				accept(rgb);
			}
			writeBytes(element.colormapPixelData);
		}

		override public function visitColorMatrixFilter(element:ColorMatrixFilter):void
		{
			for (var i:int = 0; i < 20; i++)
			{
				_out.writeFLOAT32(element.matrix[i]);
			}
		}

		override public function visitConvolutionFilter(element:ConvolutionFilter):void
		{
			_out.writeUI8(element.matrixX);
			_out.writeUI8(element.matrixY);
			_out.writeFLOAT32(element.divisor);
			_out.writeFLOAT32(element.bias);

			for each (var n:Number in element.matrix)
			{
				_out.writeFLOAT32(n);
			}

			_out.writeUB(6, 0);
			_out.writeFLAG(element.clamp);
			_out.writeFLAG(element.preserveAlpha);
		}

		override public function visitDebugID(element:DebugID):void
		{
			writeBytes(element.uuid);
		}

		override public function visitDefineBinaryData(element:DefineBinaryData):void
		{
			_out.writeUI16(element.tag);
			_out.writeUI32(0);
			writeBytes(element.data);
		}

		override public function visitDefineBits(element:DefineBits):void
		{
			_out.writeUI16(element.characterId);
			writeBytes(element.jpegData);
		}

		override public function visitDefineBitsJPEG2(element:DefineBitsJPEG2):void
		{
			_out.writeUI16(element.characterId);
			writeBytes(element.imageData);
		}

		override public function visitDefineBitsJPEG3(element:DefineBitsJPEG3):void
		{
			_out.writeUI16(element.characterId);

			var hasData:Boolean = element.imageData !== null || element.bitmapAlphaData !== null;
			if (hasData)
			{
				_out.writeUI32(element.imageData.length);
				writeBytes(element.imageData);
				writeBytes(element.bitmapAlphaData);
			}
			else
			{
				_out.writeUI32(0);
			}
		}

		override public function visitDefineBitsJPEG4(element:DefineBitsJPEG4):void
		{
			_out.writeUI16(element.characterId);

			if (element.imageData !== null && element.bitmapAlphaData !== null)
			{
				_out.writeUI32(element.imageData.length);
				_out.writeUI16(element.deblockingParam);
				writeBytes(element.imageData);
				writeBytes(element.bitmapAlphaData);
			}
			else
			{
				_out.writeUI32(0);
				_out.writeUI16(element.deblockingParam);
			}
		}

		override public function visitDefineBitsLossless(element:DefineBitsLossless):void
		{
			_out.writeUI16(element.characterId);
			_out.writeUI8(element.format);
			_out.writeUI16(element.bitmapWidth);
			_out.writeUI16(element.bitmapHeight);

			if (element.format === BitmapType.COLOR_MAP_DATA)
			{
				var colorMapData:ColorMapData = element.zlibBitmapData as ColorMapData;
				if (colorMapData.colorTableRGB === null)
				{
					_out.writeUI8(0);
				}
				else
				{
					_out.writeUI8(colorMapData.colorTableRGB.length - 1);
				}
			}

			pushBytes();
			accept(element.zlibBitmapData);
			var bytes:ByteArray = popBytes();
			bytes.compress();
			writeBytes(bytes);
		}

		override public function visitDefineBitsLossless2(element:DefineBitsLossless2):void
		{
			_out.writeUI16(element.characterId);
			_out.writeUI8(element.format);
			_out.writeUI16(element.bitmapWidth);
			_out.writeUI16(element.bitmapHeight);

			if (element.format === BitmapType.COLOR_MAP_DATA)
			{
				var colorMapData:AlphaColorMapData = element.zlibBitmapData as AlphaColorMapData;
				if (colorMapData.colorTableRGBA === null)
				{
					_out.writeUI8(0);
				}
				else
				{
					_out.writeUI8(colorMapData.colorTableRGBA.length - 1);
				}
			}

			pushBytes();
			accept(element.zlibBitmapData);
			var bytes:ByteArray = popBytes();
			bytes.compress();
			writeBytes(bytes);
		}

		override public function visitDefineButton(element:DefineButton):void
		{
			_out.writeUI16(element.buttonId);

			for each (var record:ButtonRecord in element.characters)
			{
				accept(record);
			}

			_out.writeUI8(0);
			accept(element.actions);
			_out.writeUI8(0);
		}

		override public function visitDefineButton2(element:DefineButton2):void
		{
			var bytes:ByteArray;
			var len:uint = element.actions.length;
			var i:uint;

			_out.writeUI16(element.buttonId);
			_out.writeUB(7, 0);
			_out.writeFLAG(element.trackAsMenu);

			pushBytes();

			for each (var record:ButtonRecord in element.characters)
			{
				accept(record);
			}
			_out.writeUI8(0);

			if (len > 0)
			{
				bytes = popBytes();
				_out.writeUI16(bytes.length + 2);
				writeBytes(bytes);

				for (i = 0; i < len; i++)
				{
					var action:ButtonCondAction = element.actions[i];

					pushBytes();

					_out.writeFLAG(action.condIdleToOverDown);
					_out.writeFLAG(action.condOutDownToIdle);
					_out.writeFLAG(action.condOutDownToOverDown);
					_out.writeFLAG(action.condOverDownToOutDown);
					_out.writeFLAG(action.condOverDownToOverUp);
					_out.writeFLAG(action.condOverUpToOverDown);
					_out.writeFLAG(action.condOverUpToIdle);
					_out.writeFLAG(action.condIdleToOverUp);
					_out.writeUB(7, action.condKeyPress);
					_out.writeFLAG(action.condOverDownToIdle);
					accept(action.actions);

					bytes = popBytes();
					_out.writeUI16(i === len - 1 ? 0 : bytes.length + 3);
					writeBytes(bytes);
					_out.writeUI8(0);
				}
			}
			else
			{
				bytes = popBytes();
				_out.writeUI16(0);
				writeBytes(bytes);
			}
		}

		override public function visitDefineButtonCxform(element:DefineButtonCxform):void
		{
			_out.writeUI16(element.buttonId);
			accept(element.buttonColorTransform);
		}

		override public function visitDefineButtonSound(element:DefineButtonSound):void
		{
			_out.writeUI16(element.buttonId);
			_out.writeUI16(element.buttonSoundChar0);
			if (element.buttonSoundChar0 !== 0)
			{
				accept(element.buttonSoundInfo0);
			}
			_out.writeUI16(element.buttonSoundChar1);
			if (element.buttonSoundChar1 !== 0)
			{
				accept(element.buttonSoundInfo1);
			}
			_out.writeUI16(element.buttonSoundChar2);
			if (element.buttonSoundChar2 !== 0)
			{
				accept(element.buttonSoundInfo2);
			}
			_out.writeUI16(element.buttonSoundChar3);
			if (element.buttonSoundChar3 !== 0)
			{
				accept(element.buttonSoundInfo3);
			}
		}

		override public function visitDefineEditText(element:DefineEditText):void
		{
			_out.writeUI16(element.characterId);
			accept(element.bounds);
			_out.resetCursor();

			_out.writeFLAG(element.hasText);
			_out.writeFLAG(element.wordWrap);
			_out.writeFLAG(element.multiline);
			_out.writeFLAG(element.password);
			_out.writeFLAG(element.readOnly);
			_out.writeFLAG(element.hasTextColor);
			_out.writeFLAG(element.hasMaxLength);
			_out.writeFLAG(element.hasFont);
			_out.writeFLAG(element.hasFontClass);
			_out.writeFLAG(element.autoSize);
			_out.writeFLAG(element.hasLayout);
			_out.writeFLAG(element.noSelect);
			_out.writeFLAG(element.border);
			_out.writeFLAG(element.wasStatic);
			_out.writeFLAG(element.html);
			_out.writeFLAG(element.useOutlines);

			if (element.hasFont)
			{
				_out.writeUI16(element.fontId);
			}
			if (element.hasFontClass)
			{
				writeString(element.fontClass);
			}
			if (element.hasFont)
			{
				_out.writeUI16(element.fontHeight);
			}
			if (element.hasTextColor)
			{
				accept(element.textColor);
			}
			if (element.hasMaxLength)
			{
				_out.writeUI16(element.maxLength);
			}
			if (element.hasLayout)
			{
				_out.writeUI8(element.align);
				_out.writeUI16(element.leftMargin);
				_out.writeUI16(element.rightMargin);
				_out.writeUI16(element.indent);
				_out.writeSI16(element.leading);
			}
			writeString(element.variableName);
			if (element.hasText)
			{
				writeString(element.initialText);
			}
		}

		override public function visitDefineFont(element:DefineFont):void
		{
			_out.writeUI16(element.fontId);
			_out.writeUI16(element.numGlyphs * 2);

			var first:Boolean = true;
			for each (var offset:uint in element.offsetTable)
			{
				if (first)
				{
					first = false;
					continue;
				}
				_out.writeUI16(offset);
			}
			for each (var glyph:Shape in element.glyphShapeTable)
			{
				accept(glyph);
			}
		}

		override public function visitDefineFont2(element:DefineFont2):void
		{
			encodeDefineFont2Or3(element);
		}

		override public function visitDefineFont3(element:DefineFont3):void
		{
			encodeDefineFont2Or3(element);
		}

		override public function visitDefineFont4(element:DefineFont4):void
		{
			_out.writeUI16(element.fontId);
			_out.writeUB(5, 0);
			_out.writeFLAG(element.hasFontData);
			_out.writeFLAG(element.italic);
			_out.writeFLAG(element.bold);
			writeString(element.fontName);
			writeBytes(element.fontData);
		}

		override public function visitDefineFontAlignZones(element:DefineFontAlignZones):void
		{
			_out.writeUI16(element.fontId);
			_out.writeUB(2, element.cmsTableHint);
			_out.writeUB(6, 0);

			for each (var record:ZoneRecord in element.zoneTable)
			{
				accept(record);
			}
		}

		override public function visitDefineFontInfo(element:DefineFontInfo):void
		{
			_out.writeUI16(element.fontId);

			pushBytes();
			writeString(element.fontName, false);
			var bytes:ByteArray = popBytes();
			_out.writeUI8(bytes.length);
			writeBytes(bytes);

			_out.writeUB(2, 0);
			_out.writeFLAG(element.smallText);
			_out.writeFLAG(element.shiftJIS);
			_out.writeFLAG(element.ansi);
			_out.writeFLAG(element.italic);
			_out.writeFLAG(element.bold);
			_out.writeFLAG(element.wide);

			var code:uint;
			if (element.wide)
			{
				for each (code in element.codeTable)
				{
					_out.writeUI16(code);
				}
			}
			else
			{
				for each (code in element.codeTable)
				{
					_out.writeUI8(code);
				}
			}
		}

		override public function visitDefineFontInfo2(element:DefineFontInfo2):void
		{
			_out.writeUI16(element.fontId);

			pushBytes();
			writeString(element.fontName, false);
			var bytes:ByteArray = popBytes();
			_out.writeUI8(bytes.length);
			writeBytes(bytes);

			_out.writeUB(2, 0);
			_out.writeFLAG(element.smallText);
			_out.writeFLAG(element.shiftJIS);
			_out.writeFLAG(element.ansi);
			_out.writeFLAG(element.italic);
			_out.writeFLAG(element.bold);
			_out.writeUB(1, 1);

			accept(element.languageCode);
			for each (var code:uint in element.codeTable)
			{
				_out.writeUI16(code);
			}
		}

		override public function visitDefineFontName(element:DefineFontName):void
		{
			_out.writeUI16(element.fontId);
			writeString(element.fontName);
			writeString(element.fontCopyright);
		}

		override public function visitDefineMorphShape(element:DefineMorphShape):void
		{
			_out.writeUI16(element.characterId);
			accept(element.startBounds);
			accept(element.endBounds);
			pushBytes();
			encodeMorphFillStyleArray(element.morphFillStyles);
			encodeMorphLineStyleArray(element.morphLineStyles);

			element.startEdges.fillMinBits = getMinUBits(element.startEdges.fillMinBits, element.morphFillStyles.length);
			element.startEdges.lineMinBits = getMinUBits(element.startEdges.lineMinBits, element.morphLineStyles.length);

			accept(element.startEdges);
			var bytes:ByteArray = popBytes();
			_out.writeUI32(bytes.length);
			_out.writeBytes(bytes);
			accept(element.endEdges);
		}

		override public function visitDefineMorphShape2(element:DefineMorphShape2):void
		{
			_out.writeUI16(element.characterId);
			accept(element.startBounds);
			accept(element.endBounds);
			accept(element.startEdgeBounds);
			accept(element.endEdgeBounds);
			_out.writeUB(6, 0);
			_out.writeFLAG(element.usesNonScalingStrokes);
			_out.writeFLAG(element.usesScalingStrokes);
			pushBytes();
			encodeMorphFillStyleArray(element.morphFillStyles);
			encodeMorphLineStyleArray(element.morphLineStyles);
			accept(element.startEdges);
			var bytes:ByteArray = popBytes();
			_out.writeUI32(bytes.length);
			_out.writeBytes(bytes);
			accept(element.endEdges);
		}

		override public function visitDefineScalingGrid(element:DefineScalingGrid):void
		{
			_out.writeUI16(element.characterId);
			accept(element.splitter);
		}

		override public function visitDefineSceneAndFrameLabelData(element:DefineSceneAndFrameLabelData):void
		{
			_out.writeEncodedU32(element.sceneData.length);
			for each (var scene:SceneData in element.sceneData)
			{
				_out.writeEncodedU32(scene.frameOffset);
				writeString(scene.name);
			}

			_out.writeEncodedU32(element.frameLabelData.length);
			for each (var frame:FrameLabelData in element.frameLabelData)
			{
				_out.writeEncodedU32(frame.frameNumber);
				writeString(frame.label);
			}
		}

		override public function visitDefineShape(element:DefineShape):void
		{
			_out.writeUI16(element.shapeId);
			accept(element.shapeBounds);
			accept(element.shapes);
		}

		override public function visitDefineShape2(element:DefineShape2):void
		{
			_out.writeUI16(element.shapeId);
			accept(element.shapeBounds);
			accept(element.shapes);
		}

		override public function visitDefineShape3(element:DefineShape3):void
		{
			_out.writeUI16(element.shapeId);
			accept(element.shapeBounds);
			accept(element.shapes);
		}

		override public function visitDefineShape4(element:DefineShape4):void
		{
			_out.writeUI16(element.shapeId);
			accept(element.shapeBounds);
			accept(element.edgeBounds);
			_out.resetCursor();
			_out.writeUB(5, 0);
			_out.writeFLAG(element.usesFillWindingRule);
			_out.writeFLAG(element.usesNonScalingStrokes);
			_out.writeFLAG(element.usesScalingStrokes);
			accept(element.shapes);
		}

		override public function visitDefineSound(element:DefineSound):void
		{
			_out.writeUI16(element.soundId);
			_out.writeUB(4, element.soundFormat);
			_out.writeUB(2, element.soundRate);
			_out.writeUB(1, element.soundSize);
			_out.writeUB(1, element.soundType);
			_out.writeUI32(element.soundSampleCount);
			writeBytes(element.soundData);
		}

		override public function visitDefineSprite(element:DefineSprite):void
		{
			_out.writeUI16(element.spriteId);
			_out.writeUI16(element.frameCount);
			encodeTags(element.tags);
		}

		override public function visitDefineText(element:DefineText):void
		{
			_out.writeUI16(element.characterId);
			accept(element.textBounds);
			accept(element.textMatrix);
			_out.writeUI8(element.glyphBits);
			_out.writeUI8(element.advanceBits);
			encodeTextRecords(element.glyphBits, element.advanceBits, element.textRecords);
			_out.writeUI8(0);
		}

		override public function visitDefineText2(element:DefineText2):void
		{
			_out.writeUI16(element.characterId);
			accept(element.textBounds);
			accept(element.textMatrix);
			_out.writeUI8(element.glyphBits);
			_out.writeUI8(element.advanceBits);
			encodeTextRecords(element.glyphBits, element.advanceBits, element.textRecords);
			_out.writeUI8(0);
		}

		override public function visitDefineVideoStream(element:DefineVideoStream):void
		{
			_out.writeUI16(element.characterId);
			_out.writeUI16(element.numFrames);
			_out.writeUI16(element.width);
			_out.writeUI16(element.height);
			_out.writeUB(4, 0);
			_out.writeUB(3, element.deblocking);
			_out.writeFLAG(element.smoothing);
			_out.writeUI8(element.codecId);
		}

		override public function visitDoABC(element:DoABC):void
		{
			accept(element.abcData);
		}

		override public function visitDoABC2(element:DoABC2):void
		{
			_out.writeUI32(element.flags);
			writeString(element.name);
			accept(element.abcData);
		}

		override public function visitDoAction(element:DoAction):void
		{
			accept(element.actions);
			_out.writeUI8(0);
		}

		override public function visitDoInitAction(element:DoInitAction):void
		{
			_out.writeUI16(element.spriteId);
			accept(element.actions);
			_out.writeUI8(0);
		}

		override public function visitDropShadowFilter(element:DropShadowFilter):void
		{
			accept(element.dropShadowColor);
			_out.writeFIXED(element.blurX);
			_out.writeFIXED(element.blurY);
			_out.writeFIXED(element.angle);
			_out.writeFIXED(element.distance);
			_out.writeFIXED8(element.strength);
			_out.writeFLAG(element.innerShadow);
			_out.writeFLAG(element.knockout);
			_out.writeFLAG(element.compositeSource);
			_out.writeUB(5, element.passes);
		}

		override public function visitEnableDebugger(element:EnableDebugger):void
		{
			writeBytes(element.password);
		}

		override public function visitEnableDebugger2(element:EnableDebugger2):void
		{
			_out.writeUI16(element.reserved);
			writeBytes(element.password);
		}

		override public function visitEnd(element:End):void
		{
		}

		override public function visitExportAssets(element:ExportAssets):void
		{
			_out.writeUI16(element.assets.length);

			for each (var asset:Asset in element.assets)
			{
				_out.writeUI16(asset.tag);
				writeString(asset.name);
			}
		}

		override public function visitFileAttributes(element:FileAttributes):void
		{
			_out.writeUB(1, 0);
			_out.writeFLAG(element.useDirectBlit);
			_out.writeFLAG(element.useGPU);
			_out.writeFLAG(element.hasMetadata);
			_out.writeFLAG(element.actionScript3);
			_out.writeFLAG(element.suppressCrossDomainCaching);
			_out.writeFLAG(element.swfRelativeUrls);
			_out.writeFLAG(element.useNetwork);
			_out.writeUB(24, 0);
		}

		override public function visitFillStyle(element:FillStyle):void
		{
			_out.writeUI8(element.fillStyleType);

			switch (element.fillStyleType)
			{
				case FillStyleType.SOLID_FILL:
					accept(element.color);
					break;
				case FillStyleType.LINEAR_GRADIENT_FILL:
				case FillStyleType.RADIAL_GRADIENT_FILL:
				case FillStyleType.FOCAL_RADIAL_GRADIENT_FILL:
					accept(element.gradientMatrix);
					accept(element.gradient);
					break;
				case FillStyleType.REPEATING_BITMAP_FILL:
				case FillStyleType.CLIPPED_BITMAP_FILL:
				case FillStyleType.NON_SMOOTHED_CLIPPED_BITMAP_FILL:
				case FillStyleType.NON_SMOOTHED_REPEATING_BITMAP_FILL:
					_out.writeUI16(element.bitmapId);
					accept(element.bitmapMatrix);
					break;
			}
		}

		override public function visitFocalGradient(element:FocalGradient):void
		{
			_out.resetCursor();

			_out.writeUB(2, element.spreadMode);
			_out.writeUB(2, element.interpolationMode);
			_out.writeUB(4, element.gradientRecords.length);

			for each (var record:GradientRecord in element.gradientRecords)
			{
				accept(record);
			}

			_out.writeFIXED8(element.focalPoint);
		}

		override public function visitFrameLabel(element:FrameLabel):void
		{
			writeString(element.name);
			if (_version >= SWFVersion.VERSION_6 && element.namedAnchor)
			{
				_out.writeUI8(1);
			}
		}

		override public function visitGlowFilter(element:GlowFilter):void
		{
			accept(element.glowColor);
			_out.writeFIXED(element.blurX);
			_out.writeFIXED(element.blurY);
			_out.writeFIXED8(element.strength);
			_out.writeFLAG(element.innerGlow);
			_out.writeFLAG(element.knockout);
			_out.writeFLAG(element.compositeSource);
			_out.writeUB(5, element.passes);
		}

		override public function visitGradient(element:Gradient):void
		{
			_out.resetCursor();

			_out.writeUB(2, element.spreadMode);
			_out.writeUB(2, element.interpolationMode);
			_out.writeUB(4, element.gradientRecords.length);

			for each (var record:GradientRecord in element.gradientRecords)
			{
				accept(record);
			}
		}

		override public function visitGradientBevelFilter(element:GradientBevelFilter):void
		{
			_out.writeUI8(element.gradientColors.length);
			for (var i:uint = 0, len:uint = element.gradientColors.length; i < len; i++)
			{
				accept(element.gradientColors[i] as RGBA);
				_out.writeUI8(element.gradientRatio[i]);
			}

			_out.writeFIXED(element.blurX);
			_out.writeFIXED(element.blurY);
			_out.writeFIXED(element.angle);
			_out.writeFIXED(element.distance);
			_out.writeFIXED8(element.strength);
			_out.writeFLAG(element.innerShadow);
			_out.writeFLAG(element.knockout);
			_out.writeFLAG(element.compositeSource);
			_out.writeFLAG(element.onTop);
			_out.writeUB(4, element.passes);
		}

		override public function visitGradientGlowFilter(element:GradientGlowFilter):void
		{
			_out.writeUI8(element.gradientColors.length);
			for (var i:uint = 0, len:uint = element.gradientColors.length; i < len; i++)
			{
				accept(element.gradientColors[i] as RGBA);
				_out.writeUI8(element.gradientRatio[i]);
			}

			_out.writeFIXED(element.blurX);
			_out.writeFIXED(element.blurY);
			_out.writeFIXED(element.angle);
			_out.writeFIXED(element.distance);
			_out.writeFIXED8(element.strength);
			_out.writeFLAG(element.innerShadow);
			_out.writeFLAG(element.knockout);
			_out.writeFLAG(element.compositeSource);
			_out.writeFLAG(element.onTop);
			_out.writeUB(4, element.passes);
		}

		override public function visitGradientRecord(element:GradientRecord):void
		{
			_out.writeUI8(element.ratio);
			accept(element.color);
		}

		override public function visitHeader(element:Header):void
		{
			if (element.compressed)
			{
				_out.writeUI8(0x43);
				/*
				   //TODO LAMA
				   if(element.compressType === CompressType.LZMA){
				   _out.writeUI8(0x5A);
				   }else{
				   _out.writeUI8(0x43);
				   }
				 */
			}
			else
			{
				_out.writeUI8(0x46);
			}
			_out.writeUI8(0x57);
			_out.writeUI8(0x53);
			_out.writeUI8(element.version);

			pushBytes();

			accept(element.frameSize);
			_out.writeFIXED8(element.frameRate);
			_out.writeUI16(element.frameCount);
		}

		override public function visitImportAssets(element:ImportAssets):void
		{
			writeString(element.url);
			_out.writeUI16(element.assets.length);

			for each (var asset:Asset in element.assets)
			{
				_out.writeUI16(asset.tag);
				writeString(asset.name);
			}
		}

		override public function visitImportAssets2(element:ImportAssets2):void
		{
			writeString(element.url);
			_out.writeUI16(0);
			_out.writeUI16(element.assets.length);

			for each (var asset:Asset in element.assets)
			{
				_out.writeUI16(asset.tag);
				writeString(asset.name);
			}
		}

		override public function visitJPEGTables(element:JPEGTables):void
		{
			writeBytes(element.jpegData);
		}

		override public function visitLanguageCode(element:LanguageCode):void
		{
			_out.writeUI8(element.languageCode);
		}

		override public function visitLineStyle(element:LineStyle):void
		{
			_out.writeUI16(element.width);
			accept(element.color);
		}

		override public function visitLineStyle2(element:LineStyle2):void
		{
			_out.writeUI16(element.width);
			_out.writeUB(2, element.startCapStyle);
			_out.writeUB(2, element.joinStyle);
			_out.writeFLAG(element.hasFill);
			_out.writeFLAG(element.noHScale);
			_out.writeFLAG(element.noVScale);
			_out.writeFLAG(element.pixelHinting);
			_out.writeUB(5, 0);
			_out.writeFLAG(element.noClose);
			_out.writeUB(2, element.endCapStyle);

			if (element.joinStyle === JoinStyle.JOIN_STYLE_MITER)
			{
				_out.writeUI16(element.miterLimitFactor);
			}
			if (!element.hasFill)
			{
				accept(element.color);
			}
			else
			{
				accept(element.fillType);
			}
		}

		override public function visitMatrix(element:Matrix):void
		{
			_out.resetCursor();

			var len:uint;

			_out.writeFLAG(element.hasScale);
			if (element.hasScale)
			{
				len = getMinFBits(element.scaleMinBits, element.scaleX, element.scaleY);
				_out.writeUB(5, len);
				_out.writeFB(len, element.scaleX);
				_out.writeFB(len, element.scaleY);
			}

			_out.writeFLAG(element.hasRotate);
			if (element.hasRotate)
			{
				len = getMinFBits(element.rotateMinBits, element.rotateSkew0, element.rotateSkew1);
				_out.writeUB(5, len);
				_out.writeFB(len, element.rotateSkew0);
				_out.writeFB(len, element.rotateSkew1);
			}

			len = getMinSBits(element.transrateMinBits, element.translateX, element.translateY);
			_out.writeUB(5, len);
			_out.writeSB(len, element.translateX);
			_out.writeSB(len, element.translateY);
		}

		override public function visitMetadata(element:Metadata):void
		{
			writeString(element.metadata);
		}

		override public function visitMorphFillStyle(element:MorphFillStyle):void
		{
			_out.writeUI8(element.fillStyleType);

			switch (element.fillStyleType)
			{
				case MorphFillStyleType.SOLID_FILL:
					accept(element.startColor);
					accept(element.endColor);
					break;
				case MorphFillStyleType.LINEAR_GRADIENT_FILL:
				case MorphFillStyleType.RADIAL_GRADIENT_FILL:
					accept(element.startGradientMatrix);
					accept(element.endGradientMatrix);
					_out.writeUI8(element.gradient.length);
					for each (var gradient:MorphGradientRecord in element.gradient)
					{
						accept(gradient);
					}
					break;
				case MorphFillStyleType.REPEATING_BITMAP_FILL:
				case MorphFillStyleType.CLIPPED_BITMAP_FILL:
				case MorphFillStyleType.NON_SMOOTHED_REPEATING_BITMAP_FILL:
				case MorphFillStyleType.NON_SMOOTHED_CLIPPED_BITMAP_FILL:
					_out.writeUI16(element.bitmapId);
					accept(element.startBitmapMatrix);
					accept(element.startBitmapMatrix);
					break;
			}
		}

		override public function visitMorphGradientRecord(element:MorphGradientRecord):void
		{
			_out.writeUI8(element.startRatio);
			accept(element.startColor);
			_out.writeUI8(element.endRatio);
			accept(element.endColor);
		}

		override public function visitMorphLineStyle(element:MorphLineStyle):void
		{
			_out.writeUI16(element.startWidth);
			_out.writeUI16(element.endWidth);
			accept(element.startColor);
			accept(element.endColor);
		}

		override public function visitMorphLineStyle2(element:MorphLineStyle2):void
		{
			_out.writeUI16(element.startWidth);
			_out.writeUI16(element.endWidth);
			_out.writeUB(2, element.startCapStyle);
			_out.writeUB(2, element.joinStyle);
			_out.writeFLAG(element.hasFill);
			_out.writeFLAG(element.noHScale);
			_out.writeFLAG(element.noVScale);
			_out.writeFLAG(element.pixelHinting);
			_out.writeUB(5, 0);
			_out.writeFLAG(element.noClose);
			_out.writeUB(2, element.endCapStyle);

			if (element.joinStyle === JoinStyle.JOIN_STYLE_MITER)
			{
				_out.writeUI16(element.miterLimitFactor);
			}
			if (!element.hasFill)
			{
				accept(element.startColor);
				accept(element.endColor);
			}
			else
			{
				accept(element.fillType);
			}
		}

		override public function visitPlaceObject(element:PlaceObject):void
		{
			_out.writeUI16(element.characterId);
			_out.writeUI16(element.depth);
			accept(element.matrix);
			accept(element.colorTransform);
		}

		override public function visitPlaceObject2(element:PlaceObject2):void
		{
			_out.writeFLAG(element.hasClipAction);
			_out.writeFLAG(element.hasClipDepth);
			_out.writeFLAG(element.hasName);
			_out.writeFLAG(element.hasRatio);
			_out.writeFLAG(element.hasColorTransform);
			_out.writeFLAG(element.hasMatrix);
			_out.writeFLAG(element.hasCharacter);
			_out.writeFLAG(element.hasMove);
			_out.writeUI16(element.depth);

			if (element.hasCharacter)
			{
				_out.writeUI16(element.characterId);
			}
			if (element.hasMatrix)
			{
				accept(element.matrix);
			}
			if (element.hasColorTransform)
			{
				accept(element.colorTransform);
			}
			if (element.hasRatio)
			{
				_out.writeUI16(element.ratio);
			}
			if (element.hasName)
			{
				writeString(element.name);
			}
			if (element.hasClipDepth)
			{
				_out.writeUI16(element.clipDepth);
			}
			if (element.hasClipAction)
			{
				accept(element.clipActions);
			}
		}

		override public function visitPlaceObject3(element:PlaceObject3):void
		{
			_out.writeFLAG(element.hasClipAction);
			_out.writeFLAG(element.hasClipDepth);
			_out.writeFLAG(element.hasName);
			_out.writeFLAG(element.hasRatio);
			_out.writeFLAG(element.hasColorTransform);
			_out.writeFLAG(element.hasMatrix);
			_out.writeFLAG(element.hasCharacter);
			_out.writeFLAG(element.hasMove);
			_out.writeUB(3, 0);
			_out.writeFLAG(element.hasImage);
			_out.writeFLAG(element.hasClassName);
			_out.writeFLAG(element.hasCacheAsBitmap);
			_out.writeFLAG(element.hasBlendMode);
			_out.writeFLAG(element.hasFilterList);
			_out.writeUI16(element.depth);

			if (element.hasClassName || (element.hasImage && element.hasCharacter))
			{
				writeString(element.className);
			}
			if (element.hasCharacter)
			{
				_out.writeUI16(element.characterId);
			}
			if (element.hasMatrix)
			{
				accept(element.matrix);
			}
			if (element.hasColorTransform)
			{
				accept(element.colorTransform);
			}
			if (element.hasRatio)
			{
				_out.writeUI16(element.ratio);
			}
			if (element.hasName)
			{
				writeString(element.name);
			}
			if (element.hasClipDepth)
			{
				_out.writeUI16(element.clipDepth);
			}
			if (element.hasFilterList)
			{
				encodeFilterList(element.surfaceFilterList);
			}
			if (element.hasBlendMode)
			{
				_out.writeUI8(element.blendMode);
			}
			if (element.hasCacheAsBitmap)
			{
				_out.writeUI8(element.bitmapCache);
			}
			if (element.hasClipAction)
			{
				accept(element.clipActions);
			}
		}

		override public function visitProductInfo(element:ProductInfo):void
		{
			_out.writeSI32(element.product);
			_out.writeSI32(element.edition);
			_out.writeSI8(element.majorVersion);
			_out.writeSI8(element.minorVersion);
			_out.writeSI64(element.build);
			_out.writeSI64(element.compileDate);
		}

		override public function visitProtect(element:Protect):void
		{
			writeBytes(element.password);
		}

		override public function visitRGB(element:RGB):void
		{
			_out.writeUI8(element.red);
			_out.writeUI8(element.green);
			_out.writeUI8(element.blue);
		}

		override public function visitRGBA(element:RGBA):void
		{
			_out.writeUI8(element.red);
			_out.writeUI8(element.green);
			_out.writeUI8(element.blue);
			_out.writeUI8(element.alpha);
		}

		override public function visitRect(element:Rect):void
		{
			_out.resetCursor();

			var len:uint = getMinSBits(element.minBits, element.xMin, element.xMax, element.yMin, element.yMax);
			_out.writeUB(5, len);
			_out.writeSB(len, element.xMin);
			_out.writeSB(len, element.xMax);
			_out.writeSB(len, element.yMin);
			_out.writeSB(len, element.yMax);
		}

		override public function visitRemoveObject(element:RemoveObject):void
		{
			_out.writeUI16(element.characterId);
			_out.writeUI16(element.depth);
		}

		override public function visitRemoveObject2(element:RemoveObject2):void
		{
			_out.writeUI16(element.depth);
		}

		override public function visitSWF(element:SWF):void
		{
			_swf = element;
			_version = element.header.version;
			encode();
		}

		override public function visitScriptLimits(element:ScriptLimits):void
		{
			_out.writeUI16(element.maxRecursionDepth);
			_out.writeUI16(element.scriptTimeoutSeconds);
		}

		override public function visitSetBackgroundColor(element:SetBackgroundColor):void
		{
			accept(element.backgroundColor);
		}

		override public function visitSetTabIndex(element:SetTabIndex):void
		{
			_out.writeUI16(element.depth);
			_out.writeUI16(element.tabIndex);
		}

		override public function visitShape(element:Shape):void
		{
			_out.resetCursor();
			encodeShapeRecords(element.shapeRecords, element.fillMinBits, element.lineMinBits);
		}

		override public function visitShapeWithStyle(element:ShapeWithStyle):void
		{
			var count:uint = element.fillStyles.length;
			if (count > 0xfe)
			{
				_out.writeUI8(0xff);
				_out.writeUI16(count);
			}
			else
			{
				_out.writeUI8(count);
			}
			for each (var fillStyle:FillStyle in element.fillStyles)
			{
				accept(fillStyle);
			}
			count = element.lineStyles.length;
			if (count > 0xfe)
			{
				_out.writeUI8(0xff);
				_out.writeUI16(count);
			}
			else
			{
				_out.writeUI8(count);
			}
			for each (var lineStyle:LineStyleBase in element.lineStyles)
			{
				accept(lineStyle);
			}
			_out.resetCursor();

			var numFillBits:uint = getMinUBits(element.fillMinBits, element.fillStyles.length);
			var numLineBits:uint = getMinUBits(element.lineMinBits, element.lineStyles.length);
			encodeShapeRecords(element.shapeRecords, numFillBits, numLineBits);
		}

		override public function visitShowFrame(element:ShowFrame):void
		{
		}

		override public function visitSoundEnvelope(element:SoundEnvelope):void
		{
			_out.writeUI32(element.pos44);
			_out.writeUI16(element.leftLevel);
			_out.writeUI16(element.rightLevel);
		}

		override public function visitSoundInfo(element:SoundInfo):void
		{
			_out.resetCursor();

			_out.writeUB(2, 0);
			_out.writeFLAG(element.syncStop);
			_out.writeFLAG(element.syncNoMultiple);
			_out.writeFLAG(element.hasEnvelope);
			_out.writeFLAG(element.hasLoops);
			_out.writeFLAG(element.hasOutPoint);
			_out.writeFLAG(element.hasInPoint);

			if (element.hasInPoint)
			{
				_out.writeUI32(element.inPoint);
			}
			if (element.hasOutPoint)
			{
				_out.writeUI32(element.outPoint);
			}
			if (element.hasLoops)
			{
				_out.writeUI16(element.loopCount);
			}
			if (element.hasEnvelope)
			{
				_out.writeUI8(element.envelopeRecords.length);
				for each (var record:SoundEnvelope in element.envelopeRecords)
				{
					accept(record);
				}
			}
		}

		override public function visitSoundStreamBlock(element:SoundStreamBlock):void
		{
			writeBytes(element.streamSoundData);
		}

		override public function visitSoundStreamHead(element:SoundStreamHead):void
		{
			_out.writeUB(4, element.reserved);
			_out.writeUB(2, element.playbackSoundRate);
			_out.writeUB(1, element.playbackSoundSize);
			_out.writeUB(1, element.playbackSoundType);
			_out.writeUB(4, element.streamSoundCompression);
			_out.writeUB(2, element.streamSoundRate);
			_out.writeUB(1, element.streamSoundSize);
			_out.writeUB(1, element.streamSoundType);
			_out.writeUI16(element.streamSoundSampleCount);

			if (element.streamSoundCompression === SoundFormat.MP3)
			{
				_out.writeSI16(element.latencySeek);
			}
		}

		override public function visitSoundStreamHead2(element:SoundStreamHead2):void
		{
			_out.writeUB(4, element.reserved);
			_out.writeUB(2, element.playbackSoundRate);
			_out.writeUB(1, element.playbackSoundSize);
			_out.writeUB(1, element.playbackSoundType);
			_out.writeUB(4, element.streamSoundCompression);
			_out.writeUB(2, element.streamSoundRate);
			_out.writeUB(1, element.streamSoundSize);
			_out.writeUB(1, element.streamSoundType);
			_out.writeUI16(element.streamSoundSampleCount);

			if (element.streamSoundCompression === SoundFormat.MP3)
			{
				_out.writeSI16(element.latencySeek);
			}
		}

		override public function visitStartSound(element:StartSound):void
		{
			_out.writeUI16(element.soundId);
			accept(element.soundInfo);
		}

		override public function visitStartSound2(element:StartSound2):void
		{
			writeString(element.soundClassName);
			accept(element.soundInfo);
		}

		override public function visitSymbolClass(element:SymbolClass):void
		{
			_out.writeUI16(element.symbols.length);

			for each (var symbol:Symbol in element.symbols)
			{
				_out.writeUI16(symbol.tag);
				writeString(symbol.name);
			}
		}

		override public function visitUnknownTag(element:UnknownTag):void
		{
			writeBytes(element.bytes);
		}

		override public function visitVideoFrame(element:VideoFrame):void
		{
			_out.writeUI16(element.streamId);
			_out.writeUI16(element.frameNumber);
			writeBytes(element.videoData);
		}

		override public function visitZoneData(element:ZoneData):void
		{
			_out.writeFLOAT16(element.alignmentCoordinate);
			_out.writeFLOAT16(element.range);
		}

		override public function visitZoneRecord(element:ZoneRecord):void
		{
			_out.writeUI8(element.zoneData.length);

			for each (var data:ZoneData in element.zoneData)
			{
				accept(data);
			}

			_out.writeUB(6, 0);
			_out.writeFLAG(element.zoneMaskY);
			_out.writeFLAG(element.zoneMaskX);
		}

		private function accept(element:ISWFElement):void
		{
			if (element !== null)
			{
				element.accept(this);
			}
		}

		private function compressSWF(header:Header, bytes:ByteArray):void
		{
			//TODO LZMA
			if (_swf.header.compressed)
			{
				bytes.compress();
			}
		}

		private function get currentTag():int
		{
			if (_tagStack.length === 0)
			{
				return -1;
			}

			return _tagStack[_tagStack.length - 1];
		}

		private function encode():void
		{
			accept(_swf.header);
			encodeTags(_swf.tags);

			var bytes:ByteArray = popBytes();
			var length:uint = bytes.length;
			compressSWF(_swf.header, bytes);

			_out.writeUI32(length + 8);
			writeBytes(bytes);
		}

		private function encodeDefineFont2Or3(element:DefineFont23Base):void
		{
			_out.writeUI16(element.fontId);
			_out.writeFLAG(element.hasLayout);
			_out.writeFLAG(element.shiftJIS);
			_out.writeFLAG(element.smallText);
			_out.writeFLAG(element.ansi);
			_out.writeFLAG(element.wideOffset);
			_out.writeFLAG(element.wideCodes);
			_out.writeFLAG(element.italic);
			_out.writeFLAG(element.bold);
			accept(element.languageCode);

			pushBytes();
			writeString(element.fontName, false);
			var bytes:ByteArray = popBytes();
			_out.writeUI8(bytes.length);
			writeBytes(bytes);

			var numGlyphs:uint = element.numGlyphs;
			_out.writeUI16(element.numGlyphs);

			var code:uint;
			if (element.wideOffset)
			{
				for each (code in element.offsetTable)
				{
					_out.writeUI32(code);
				}
			}
			else
			{
				for each (code in element.offsetTable)
				{
					_out.writeUI16(code);
				}
			}

			pushBytes();
			for each (var shape:Shape in element.glyphShapeTable)
			{
				accept(shape);
			}
			bytes = popBytes();

			if (element.wideOffset)
			{
				_out.writeUI32(bytes.length + 4 + numGlyphs * 4);
			}
			else
			{
				_out.writeUI16(bytes.length + 2 + numGlyphs * 2);
			}
			writeBytes(bytes);

			if (element.wideCodes)
			{
				for each (code in element.codeTable)
				{
					_out.writeUI16(code);
				}
			}
			else
			{
				for each (code in element.codeTable)
				{
					_out.writeUI8(code);
				}
			}

			if (element.hasLayout)
			{
				_out.writeSI16(element.ascent);
				_out.writeSI16(element.descent);
				_out.writeSI16(element.leading);

				for each (var a:int in element.advanceTable)
				{
					_out.writeSI16(a);
				}
				for each (var rect:Rect in element.boundsTable)
				{
					accept(rect);
				}
				_out.writeUI16(element.kerningTable.length);

				var kerning:KerningRecord;
				if (element.wideCodes)
				{
					for each (kerning in element.kerningTable)
					{
						_out.writeUI16(kerning.fontKerningCode1);
						_out.writeUI16(kerning.fontKerningCode2);
						_out.writeSI16(kerning.fontKerningAdjustment);
					}
				}
				else
				{
					for each (kerning in element.kerningTable)
					{
						_out.writeUI8(kerning.fontKerningCode1);
						_out.writeUI8(kerning.fontKerningCode2);
						_out.writeSI16(kerning.fontKerningAdjustment);
					}
				}
			}
		}

		private function encodeFilterList(filterList:Array):void
		{
			_out.writeUI8(filterList.length);

			for each (var filter:IFilter in filterList)
			{
				_out.writeUI8(filter.filterType);
				accept(filter);
			}
		}

		private function encodeMorphFillStyleArray(fillStyles:Array):void
		{
			var count:uint = fillStyles.length;
			if (count > 0xfe)
			{
				_out.writeUI8(0xff);
				_out.writeUI16(count);
			}
			else
			{
				_out.writeUI8(count);
			}

			for each (var record:MorphFillStyle in fillStyles)
			{
				accept(record);
			}
		}

		private function encodeMorphLineStyleArray(lineStyles:Array):void
		{
			var count:uint = lineStyles.length;
			if (count > 0xfe)
			{
				_out.writeUI8(0xff);
				_out.writeUI16(count);
			}
			else
			{
				_out.writeUI8(count);
			}

			for each (var record:MorphLineStyleBase in lineStyles)
			{
				accept(record);
			}
		}

		private function encodeShapeRecords(records:Array, numFillBits:uint, numLineBits:uint):void
		{
			var minBits:uint;
			_out.writeUB(4, numFillBits);
			_out.writeUB(4, numLineBits);

			for (var i:uint = 0, len:uint = records.length; i < len; i++)
			{
				var record:IShapeRecord = records[i];
				var flag:uint;

				if (record is StraightEdgeRecord)
				{
					var straightEdge:StraightEdgeRecord = record as StraightEdgeRecord;
					var dx:int = (straightEdge.generalLine || !straightEdge.vertLine) ? straightEdge.deltaX : 0;
					var dy:int = (straightEdge.generalLine || straightEdge.vertLine) ? straightEdge.deltaY : 0;

					minBits = getMinSBits(straightEdge.minBits, dx, dy);
					if (minBits < 2)
					{
						minBits = 2;
					}

					flag = 32 | 16;
					flag |= 0xf & (minBits - 2);
					_out.writeUB(6, flag);
					_out.writeFLAG(straightEdge.generalLine);
					if (!straightEdge.generalLine)
					{
						_out.writeFLAG(straightEdge.vertLine);
					}

					if (straightEdge.generalLine || !straightEdge.vertLine)
					{
						_out.writeSB(minBits, straightEdge.deltaX);
					}
					if (straightEdge.generalLine || straightEdge.vertLine)
					{
						_out.writeSB(minBits, straightEdge.deltaY);
					}
				}
				else if (record is CurvedEdgeRecord)
				{
					var curvedEdge:CurvedEdgeRecord = record as CurvedEdgeRecord;

					flag = 32;
					minBits = getMinSBits(curvedEdge.minBits, curvedEdge.controlDeltaX, curvedEdge.controlDeltaY, curvedEdge.anchorDeltaX, curvedEdge.anchorDeltaY);
					if (minBits < 2)
					{
						minBits = 2;
					}

					flag |= 0xf & (minBits - 2);
					_out.writeUB(6, flag);
					_out.writeSB(minBits, curvedEdge.controlDeltaX);
					_out.writeSB(minBits, curvedEdge.controlDeltaY);
					_out.writeSB(minBits, curvedEdge.anchorDeltaX);
					_out.writeSB(minBits, curvedEdge.anchorDeltaY);
				}
				else if (record is StyleChangeRecord)
				{
					var styleChange:StyleChangeRecord = record as StyleChangeRecord;

					flag = 0;
					flag |= styleChange.stateMoveTo ? 1 : 0;
					flag |= styleChange.stateFillStyle0 ? 2 : 0;
					flag |= styleChange.stateFillStyle1 ? 4 : 0;
					flag |= styleChange.stateLineStyle ? 8 : 0;
					flag |= styleChange.stateNewStyles ? 16 : 0;
					_out.writeUB(6, flag);

					if (styleChange.stateMoveTo)
					{
						minBits = getMinSBits(styleChange.moveMinBits, styleChange.moveDeltaX, styleChange.moveDeltaY);
						_out.writeUB(5, minBits);
						_out.writeSB(minBits, styleChange.moveDeltaX);
						_out.writeSB(minBits, styleChange.moveDeltaY);
					}
					if (styleChange.stateFillStyle0)
					{
						_out.writeUB(numFillBits, styleChange.fillStyle0);
					}
					if (styleChange.stateFillStyle1)
					{
						_out.writeUB(numFillBits, styleChange.fillStyle1);
					}
					if (styleChange.stateLineStyle)
					{
						_out.writeUB(numLineBits, styleChange.lineStyle);
					}
					if (styleChange.stateNewStyles)
					{
						var count:uint = styleChange.fillStyles.length;
						if (count > 0xfe)
						{
							_out.writeUI8(0xff);
							_out.writeUI16(count);
						}
						else
						{
							_out.writeUI8(count);
						}
						for each (var fill:FillStyle in styleChange.fillStyles)
						{
							accept(fill);
						}

						count = styleChange.lineStyles.length;
						if (count > 0xfe)
						{
							_out.writeUI8(0xff);
							_out.writeUI16(count);
						}
						else
						{
							_out.writeUI8(count);
						}
						for each (var line:LineStyleBase in styleChange.lineStyles)
						{
							accept(line);
						}

						numFillBits = getMinUBits(styleChange.fillMinBits, styleChange.fillStyles.length);
						numLineBits = getMinUBits(styleChange.lineMinBits, styleChange.lineStyles.length);
						_out.writeUB(4, numFillBits);
						_out.writeUB(4, numLineBits);
					}
				}
				else
				{
					_out.writeUB(6, 0);
					_out.resetCursor();
				}
			}
		}

		private function encodeTags(tags:Array):void
		{
			for each (var tag:ITag in tags)
			{
				_tagStack.push(tag.code);
				pushBytes();
				accept(tag);
				_tagStack.pop();

				var bytes:ByteArray = popBytes();
				var length:uint = bytes.length;
				var longHeader:Boolean = tag.longHeader;
				var codeAndLength:uint = tag.code << 6;

				if (longHeader || length > 0x3e)
				{
					codeAndLength |= 0x3f;
				}
				else
				{
					codeAndLength |= length;
				}
				_out.writeUI16(codeAndLength);
				if (longHeader || length > 0x3e)
				{
					_out.writeUI32(length);
				}

				writeBytes(bytes);
			}
		}

		private function encodeTextRecords(glyphBits:uint, advanceBits:uint, records:Array):void
		{
			for each (var record:TextRecord in records)
			{
				_out.resetCursor();
				_out.writeUB(1, 1);
				_out.writeUB(3, 0);
				_out.writeFLAG(record.hasFont);
				_out.writeFLAG(record.hasColor);
				_out.writeFLAG(record.hasYOffset);
				_out.writeFLAG(record.hasXOffset);

				if (record.hasFont)
				{
					_out.writeUI16(record.fontId);
				}
				if (record.hasColor)
				{
					accept(record.textColor);
				}
				if (record.hasXOffset)
				{
					_out.writeSI16(record.xOffset);
				}
				if (record.hasYOffset)
				{
					_out.writeSI16(record.yOffset);
				}
				if (record.hasFont)
				{
					_out.writeUI16(record.textHeight);
				}

				_out.writeUI8(record.glyphEntries.length);
				for each (var entry:GlyphEntry in record.glyphEntries)
				{
					_out.writeUB(glyphBits, entry.glyphIndex);
					_out.writeUB(advanceBits, entry.glyphAdvance);
				}
			}
		}

		private function getMinFBits(defaultBits:uint, a:Number, b:Number = 0, c:Number = 0, d:Number = 0):uint
		{
			CONFIG::TEST
			{
				return defaultBits;
			}

			var x:uint = ((a = int(a * 65536)) < 0 ? -a : a) | ((b = int(b * 65536)) < 0 ? -b : b) | ((c = int(c * 65536)) < 0 ? -c : c) | ((d = int(d * 65536)) < 0 ? -d : d);

			x |= x >> 1 | 1;
			x |= x >> 2;
			x |= x >> 4;
			x |= x >> 8;
			x |= x >> 16;
			x -= x >>> 1 & 0x55555555;
			x = (x & 0x33333333) + (x >>> 2 & 0x33333333);
			x = (x + (x >>> 4)) & 0xf0f0f0f;
			x += x >>> 8;
			x += x >>> 16;
			return (x & 0x3f) + 1;
		}

		private function getMinSBits(defaultBits:uint, a:int, b:int = 0, c:int = 0, d:int = 0):uint
		{
			CONFIG::TEST
			{
				return defaultBits;
			}

			var x:uint = (a < 0 ? -a : a) | (b < 0 ? -b : b) | (c < 0 ? -c : c) | (d < 0 ? -d : d);

			x |= x >> 1 | 1;
			x |= x >> 2;
			x |= x >> 4;
			x |= x >> 8;
			x |= x >> 16;
			x -= x >>> 1 & 0x55555555;
			x = (x & 0x33333333) + (x >>> 2 & 0x33333333);
			x = (x + (x >>> 4)) & 0xf0f0f0f;
			x += x >>> 8;
			x += x >>> 16;
			return (x & 0x3f) + 1;
		}

		private function getMinUBits(defaultBits:uint, a:uint, b:uint = 0, c:uint = 0, d:uint = 0):uint
		{
			CONFIG::TEST
			{
				return defaultBits;
			}

			var x:uint = a | b | c | d;

			x |= x >> 1 | 1;
			x |= x >> 2;
			x |= x >> 4;
			x |= x >> 8;
			x |= x >> 16;
			x -= x >>> 1 & 0x55555555;
			x = (x & 0x33333333) + (x >>> 2 & 0x33333333);
			x = (x + (x >>> 4)) & 0xf0f0f0f;
			x += x >>> 8;
			x += x >>> 16;
			return (x & 0x3f) + 1;
		}

		private function popBytes():ByteArray
		{
			var bytes:ByteArray = _out.popBytes();
			_bytesPool.push(bytes);
			return bytes;
		}

		private function pushBytes():void
		{
			var bytes:ByteArray;

			if (_bytesPool.length === 0)
			{
				bytes = new ByteArray();
				bytes.endian = Endian.LITTLE_ENDIAN;
			}
			else
			{
				bytes = _bytesPool.pop();
				bytes.clear();
			}

			_out.pushBytes(bytes);
		}

		private function writeBytes(bytes:ByteArray):void
		{
			if (bytes !== null)
			{
				_out.writeBytes(bytes);
			}
		}

		private function writeString(value:String, nullTerminate:Boolean = true):void
		{
			if (_version <= SWFVersion.VERSION_5)
			{
				_out.writeString(value, "shift-jis", nullTerminate);
				return;
			}

			_out.writeString(value, null, nullTerminate);
		}
	}
}
